<?php
namespace  Study\IOC;
/**没有ioc时的写法*******************************************************************************************************/
class B
{
    public function doSomething()
    {
        echo 'B is doing something';
    }
}

class A{
    private $b;
    public function __construct(){
        $this->b = new B();  // A 直接创建了 B 的实例，紧耦合
    }
    public function doSomething(){
        $this->b->doSomething();
    }
}

$a = new A();
$a->doSomething();


/**有ioc时的写法*********************************************************************************************************/
class C {
    private $b;
    public function __construct(B $b){
        $this->b = $b;
    }
    public function doSomething(){
        $this->b->doSomething();
    }
}

$b = new B();
//重点:实例化应放到类的外部，而不是类的内部（尽量不修改类的内部代码）
$a = new C($b); // 将B的实例注入到A中


/**IOC简单的实现*********************************************************************************************************/
//要利用php反射类和方法

class Container{
    protected $bindings = [];

    //绑定接口或类到实际实现
    //不传 $concrete 时，表示将抽象绑定到自身，通常用于具体类，这样容器在解析时就会直接实例化这个类。
    //传入 $concrete 时，表示将抽象绑定到另一个具体实现（类名或闭包），这样容器在解析抽象时就会使用你提供的具体实现来创建对象。
    public function bind($abstract, $concrete = null)
    {

        if(is_null($concrete)){
            $concrete =  $abstract; // 绑定到自身
        }
        return $this->bindings[$abstract] = $concrete;
    }

    public function make($abstract){
        $concrete = $this->bindings[$abstract] ?? $abstract;
        // 如果是闭包，直接调用
        if ($concrete instanceof Container) {
            return $concrete($this);
        }
        // 使用反射类自动解析依赖
        $reflector = new  \ReflectionClass($abstract);

        //检查类是否可实例化
        if(!$reflector->isInstantiable()){
            throw new \Exception("{$abstract} 不能实例化");
        }

        //获取构造函数
        $constructor = $reflector->getConstructor();
        if (is_null($constructor)) {
            return new $concrete();
        }

        //获取构造参数
        $parameters = $constructor->getParameters();
        $dependencies = $this->getDependencies($parameters);
        //实例化并传入依赖
        return $reflector->newInstanceArgs($dependencies);
    }

    //解析依赖参数
    public function getDependencies($parameters){
        $dependencies = [];
        foreach($parameters as $parameter){
            //获取参数的类型
            $dependency = $parameter->getType();
            if (is_null($dependency)) {
                if($parameter->isdefaultValueAvailable()){
                    $dependencies[] = $parameter->getDefaultValue();
                }else{
                    throw new \Exception('无法解析依赖'.$parameter->name);
                }
            }else{
                //递归解析依赖
                $dependencies[] = $this->make($dependency->getName());
            }
        }
        return $dependencies;
    }
}
